[ 學習筆記系列 ] 很基礎的 JavaScript 入門 (二) - ES6 語法與 Module


Posted by ClayGao on 2020-03-20

前言

上一篇筆記了很基本的 JavaScript 常識,這一篇要繼續整理剩下的部分,如 ES6 語法,與模組等等,最後講一下 npm 與 yarn


Module ( 模組 )

概念

將常用的函式獨立成一個模組,以後有需求可以直接引入這個模組來使用。Node.js 其中就有不少內建模組可以使用 ( 內建模組前面不用加 ./ 等檔案路徑 )。  

require ( 拉來用 )

以 Node.js 內建的模組 os 為例,我可以要求使用,並使用裡面的其中一個名為 platform 的方法,官方文件表示這個函式會回傳一個字串來表示你的作業系統平台

var os = require('os')

console.log(os.platform()) // Win32 ( 乾買不起 MAC )

通常變數名稱可以自己改,但習慣上會使用你要引入的該 Module,這樣比較好辨別   

export (借出去用)

我們當然也可以自己建立一個 Module 供別人使用

假設我建立一個檔名叫做 threeTimes.js,我在裡面放一個函式,你可以取用這個模組內的函式功能 - 將帶入的值乘以 3  

第一種輸出方法 ( 好理解 )

// threeTimes.js
function triple(x) {
    return x*3
}

module.export = triple

如此一個可以供人使用的 Module 就完成了,這時我要如何引入 threeTimes.js 呢 ? 

var threeTimes = require('./threeTimes') // 加上檔案路徑,可以不加副檔名 js

console.log(triple(3)) // 9

 

第二種輸出方法

如果你一次要輸出多個函式,不只一個 triple,那你可以用物件把各函式打包送出

// threeTimes.js
function triple(x) {
    return x*3
}

function triple2(x) {
    return (x*3)*2
}

module.exports = {
    fun1 : triple,
    fun2 : triple2
}

你也可以建立一個變數,假設叫做 obj,然後將函式放入物件之中,最後使 module.exports = obj

但是要注意的是在 require 這一端的變數 threeTimes 也會是一個物件,所以使用方法上就會不同,需要使用物件的方式存取

var threeTimes = require('./threeTimes') // 加上檔案路徑,可以不加副檔名 js

console.log(threeTimes.fun1(3), threeTimes.fun2(3)) // 6 18

 

第三種輸出方法

此外,你也可以換一種輸出形式,這樣的輸出也是同上面的物件形式
exports 本身就是一個空的物件,後面所接的就像是 { } 內中的屬性

// threeTimes.js
function triple(x) {
    return x*3
}

function triple2(x) {
    return (x*3)*2
}

exports.fun1 = triple
exports.fun2 = triple2

第一種輸出方法的好處是,module.exports = 之後可以輸出你想輸出的任何東西,如一個字串或陣列,取用端可以直接引用,就如同在同一個檔案底下那樣,相當好理解,你輸出什麼,require 就是什麼。

第二種和第三種方法都是輸出一個物件,相對於取用端來說,也要用取用物件的方式做引用。  

第四種輸出方法 ( ES6 新增 )

也就是 exportimport{}export 你想輸出的東西

輸出端 threeTimes.js 如下

// threeTimes.js
export function triple(x) {
    return x*3
}

export const A = '123'

而取用端可以這樣寫 import {} from './路徑'

import {triple, A} from './threeTimes'

console.log(triple(5)) // 15
console.log(A) // '123'

 

第五種輸出方法

ES6 新增,概念上與第二種方法類似,差別在於這樣的用法也不會使輸出的東西為物件

其實就是第四種輸出方法的另一種形式,將你要輸出的東西用 { } 打包,但它並不是物件

// threeTimes.js
function triple(x) {
    return x*3
}

const A = '123'

export = {
    triple,
    A
}

如果你想使輸出的東西以你想要的新名稱輸出出去,你可以使用 as 為其取別名

// threeTimes.js
function triple(x) {
    return x*3
}

const A = '123'

export = {
    triple as fun1,
    A as str
}

或者你也可以將 as 用在輸入端

import { triple as fun1, A as str } from './threeTimes'

console.log(triple(5))

接著,如果你想引入該 Module 全部有輸出的東西,可以使用 import * as from '<./Module>'

如果這樣做,那麼輸出的部分都會被包成一個名為 的模組,所以如果要存取,必須在前面加上模組名稱

import * as allFunction from './threeTimes'

console.log(allFunction.triple(5)) // 需要先加上 allFunciton 這個模組名稱

 

第六種輸出方法 export default

// threeTimes.js
export default function triple(x) {
    return x*3
}

那取用端就可以這樣寫

import triple from './threeTimes'

console.log(triple(5))

有點像第一種使用方法,可以直接做取用,但更好的理解是,這樣的輸出方法與第四種的差別在於,import 時不用加大括號

// threeTimes.js
export default function triple(x) {
    return x*3
}

export const A = '123'

那取用端就可以這樣寫

import triple, { A } from './threeTimes' // A 要加 {},注意逗號

console.log(triple(5), A)

 

ES6 ( 由於在 2015 年發布,又稱 ES 2015 )

這邊簡單筆記 ES6 語法,但不是全部。  

const / let

const ( 常數 )

你無法對 const 建立的變數重新賦值,但若是被賦予物件型別,你仍然可以更改內中記憶體位置指向的值。  

let

作用域 ( 變數的生存範圍 )

變數運行機制會從最內層找到最外層

function abc() {
    var a = 5
    console.log(a)
}

abc()
console.log(a)

得到的結果會是 5 與一個錯誤

原因是因為第三行的 console.log() 找到了函式內層的 a,值為 5

但在函式外層的 console.log() 卻找不到內層的 a,所以顯示錯誤

let 性質

正常來說,JS 的作用域是以函式為邊界,但若是一個變數使用 let 宣告,那該變數的生存範圍就會以離他最近的區域為僅有的生存範圍,可能是 if{} 或 for loop(){}const 也有同樣性質


 

新版字串 - 淘汰舊版字串方法吧 !

多行字串

console.log(
`
    haha
    haha2
    haha3
`
)
/*
印出

  haha
  haha2
  haha3

*/

串接變數

將你想插入字串中的變數 / 物件 / 陣列等等放入 ${},再放入`之中,不用再帶入任何+` 號

var a = 100

console.log( `潮爽德撿到${a}塊內`) // 潮爽德撿到100塊內

 

解構

  • 用途:當你需要將陣列或物件的元素各個拿出來放入變數之中,可以使用

  • 陣列的解構方法:

      var [a,b,c,d,e,f,e] = [1,2,3,4,5,6]
    
      console.log(b) // 2
      console.log(e) // undefined
    

    簡而言之就是一組對一個陣列元素,b 對上 2,e 沒有可以對應的元素,所以是 undefined

  • 物件的解構方法:

      const obj = {
          a : 15,
          b : 27,
          c : 33
      }
    
      var { a,b,c } = obj
    
      console.log(a) // 15
    

    注意,物件解構時變數的名字一定要和物件的元素名稱相同,如 var { a , b } 一定要對應到該物件中的 ab,否則變數會接收不到。

    另外就是解構完之後該變數本身內含物件元素,如果這個物件元素底下還有物件,那你也可以再對該變數進行解構一次,取它的元素。

const obj = {
    a : {
        aa : 55
    },
    b : {
        bb : 66
    },
    c : 33
}

var { a : { aa },b,c } = obj
// 注意 a 與 aa,這邊是解構再解構的意思,別和物件的 {} 搞混
var { bb } = b  
// 經過上一行的解構,已經有個物件變數為 b,所以我再解構這個物件變數一次,和上一行的 a 與 aa 同意義但不同寫法

console.log(aa) // 55 , 這邊的 aa 是個變數
console.log(bb) // 66

 

展開運算子 ...

簡單來說就是在陣列變數前面加上「...」,那麼它會取消掉陣列的 [] 與性質,或是物件的 {},只展現裡面的元素。

var arr = [1,2,3,4]

console.log(...arr) // 1 2 3 4 


function sum(a,b,c,d) {
    return (( a + b + c + d ) * 2)
}
console.log(sum(...arr)) // 20

你也可以用來複製 Array

var arr = [2,3,4]
var arr2 = [...arr]

console.log(arr2) // [2,3,4]

物件也可以使用,見下例子

var obj1 = {
    x:1,
    y:2
}

var obj2 = {
    obj1,
    z:3
}

var obj3 = {
    ...obj1,
    z:3
}

console.log(obj2) // { obj1: { x: 1, y: 2 }, z: 3 }
console.log(obj3) // { x: 1, y: 2, z: 3 }

可以看到 obj2 與 obj3 的差別,很簡單地可以了解,展開運算子之於物件也是解開 {} 的束縛。

若展開之後元素有重疊,則以較之後的元素值為準

var obj1 = {
    x:1,
    y:2
}

var obj3 = {
    ...obj1,
    y:22
}

console.log(obj3) // { x: 1, y: 22 }

用展開運算子複製物件或陣列,會是一個全新的物件或陣列,新的記憶體位置與新的值,所以可以不用擔心在更改過程中改到原本被複製的物件 / 陣列的值。

簡單來說,用展開運算子複製物件 / 陣列,是複製值,也是兩個截然不同的記憶體位置。
而用一般賦值複製,如 obj2 = obj1,是複製兩個相同的記憶體位置,指向同一個值。

 

反向展開 ( Rest Parameters )

var arr = [1,2,3,4]
var [a,...rest] = arr

console.log(rest) // [2,3,4]

通常與解構搭配使用,可以理解為 rest 前的 ... 將剩下的 2,3,4 打包起來給 rest,打包的概念正如同展開的相反,故稱反向展開。

注意 ...rest 只能放在解構的最後一個區塊,你沒辦法在後面再接變數

所以 var [a,...rest, theLast ] = arr 這樣的寫法是行不通的

物件也是同樣寫法

obj1 = {
    a:1,
    b:2,
    c:3
}

var {a , ...obj2} = obj1

console.log(obj2)  // { b: 2, c: 3 }

反向展開也可以用在函式,參考下面兩個例子

function sum(...args) {
    console.log(args)
    return args[0] + args[1]
}

console.log(sum(3,5))
function sum(a,...args) {
    console.log(args)
    return a + args[0]
}

console.log(sum(3,5,50,60,70))

可以發現這樣的用法很像函式的 arguments,差別在於上述例子中使用反向展開的 args 本質是陣列,而之前的 arguments 是長得像陣列的物件

 

default Parameters

可以用在 function 的參數上,可以直接幫參數賦予預設值

function sum(a,b,c = 10) {

    return (a + b)*c
}

console.log(sum(3,5)) // 80

也可以用在解構上,替 = 左邊的變數設定預設值。

 

npm 與 yarn

npm - Node Package Manager

回想 Module 的概念,我們可以寫 Module 供自己使用,然而在世界各地也有不少人分享他們所寫的 Module 在網路上。而 npm 就是我們可以取用這些 Module 的工具,而 npm 在安裝 Node.js 的時候就會連帶安裝。

  • 安裝套件與 node_modules

在 npm 找到你想安裝的套件之後,在 CLI 使用語法安裝 : npm install <套件名稱>
這時候你的專案資料夾底下會多出 node_modules 這個資料夾,它統一集中存放你所安裝的套件

  • package.json

在安裝套件的時候之前,先輸入 npm init 並連續 Enter 跳過那些詢問,然後你可以在你的專案資料夾看到 package.json,這個就很像你的設定檔,裡面也會記錄你所這個專案所使用的 npm 套件有哪些 ( 記錄在 "dependencies" 底下)

所以如果你換了一個新的開發環境,只要持有你專屬的 package.json,然後在 CLI 輸入 npm install,npm 就會自動根據你 package.json 底下所使用的套件紀錄全部下載下來,就不用帶著沉重的套件到處跑了。

所以 node_modules 通常不會納入 Git 控管,我們會將它放入 .gitignore ,我們只要記錄 package.json 即可。

但是你安裝套件的紀錄不會自動幫你寫入 package.json 底下的 dependencies,你必須在安裝的時候使用在最後面加入 --save ( npm install <套件名稱> --save ),那這個套件將會寫入 dependencies 底下;如果你是要安裝在 devDependencies 底下,則是使用 --save-dev ( npm install <套件名稱> --save-dev )。

dependencies 與 devDependencies 的差別在於後者為開發環境才會使用到的套件。

  • package.json 底下的 script 區塊

內中可以設定你想執行的指令名稱,指令名稱後面可以指定你要執行什麼。

 "script" : {
     "start": "node index.js"
 }

這時候輸入 npm run start,就等同於輸入 node index.js

yarn 基本上和 npm 概念都相同,只列出不同的點

可以去官方網站安裝

npm yarn
npm -v yarn -v
npm install yarn install
npm install <套件名稱> yarn add <套件名稱>
npm install <套件名稱> --save yarn add <套件名稱> (即 yarn 會自動將套件寫入 package.json )

 

 結語

個人覺得模組化的部分,在日後使用框架開發都會很常用到,有時也會忘記 exportexport default 的差別,所以偶爾會回來翻資料。

很多一開始的知識不容易記住,都是因為沒有用到,但那也沒關係,曾經有個印象就好,重點是知道要從哪個方向找資料。

而 npm 與 yarn,原本想說 npm 順順用就好,但最近開始轉用 yarn 了,喜新厭舊在這時候好像變好習慣了 ? 雖然 yarn 也不新就是了,但至少用 yarn 安裝 Vue cli 不會出現錯誤 QQ

大概就是這樣了,本篇筆記結束。


#ES6 #javascript #初學者 #新手







Related Posts

The introduction and difference between class component and function component in React

The introduction and difference between class component and function component in React

Node.js 和Node.js REPL 關係

Node.js 和Node.js REPL 關係

[Day 01] 強化學習?

[Day 01] 強化學習?


Comments